Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

محاكي اختراق أخلاقي تعليمي (EEHS)

فهرس المحتويات

نظرة عامة

EEHS (Educational Ethical Hacking Simulator) تطبيق ويب يشرح مراحل اختبار الاختراق بشكل محاكٍ داخل بيئة وهمية (بيانات ونتائج افتراضية) لربط الدرس النظري بما يحدث في المختبر الحقيقي (مثل Kali + Metasploitable داخل VirtualBox). الهدف التعليمي: فهم الاستطلاع، الهجوم، والحماية، وليس أداة فعلية للهجوم.

مصطلح بمعنى مبسّط مثال مرتبط بـ EEHS
استطلاع (Reconnaissance) جمع معلومات عن أهداف قبل الاستغلال فحص Nmap يعرض منافذ من جدول sim_ports
استغلال (Exploitation) استخدام ثغرة لتحقيق هدف (مثلاً جلسة) محاكاة vsftpd في Metasploit
حماية تخفيف الخطر: جدار، تعقيم، تشفير Fail2Ban، htmlspecialchars، bcrypt

إضافة توضيحية: البرنامج لا يرسل حزم شبكية ولا يتصل بعنوان 192.168.56.102 فعلياً. الرقم 192.168.56.102 رمز يشير إلى الضحية في التقرير، والنصوص في الطرفية مبنية من الدوال في modules/*_sim.py والجداول في eehs.db.

ماذا يفعل البرنامج باختصار (للمبتدئ)

  1. تشغل الخادم بـ python app.py فيظهر الموقع على http://127.0.0.1:5000.
  2. الصفحة الرئيسية تقرأ أعداداً من جدول attack_logs وتعرض آخر 10 سجلاً وبطاقات للوحدات الست.
  3. كل وحدة لها صفحة HTML: أزرار تستدعي مسارات تبدأ بـ /api/... ترجع JSON؛ المتصفح يعرض النتائج في صندوق يشبه الطرفية (terminal) بخط monospce.
  4. قاعدة البيانات SQLite eehs.db تخزن المستخدمين الوهميين، المنافذ، التعليقات/الحمولات، وسجلاً لما يقوم به المتعلم داخل المحاكاة.
  5. لا يوجد اختراق حقيقي: كل شيء داخل البرنامج وملفه المحلي.

هيكل المشروع (شجرة + وصف)

المسار الوصف
app.py تطبيق Flask: الصفحات ومسارات api — يجمع طلبات الواجهة ويستدعي دوال modules وdatabase
database.py إنشاء eehs.db وتهيئة البيانات الافتراضية، ودالة log_attack لتسجيل الأحداث
requirements.txt الحزم: Flask, Werkzeug, bcrypt, gunicorn (للإنتاج)
render.yaml تعريف خدمة ويب Render (Blueprint) — gunicorn و$PORT
eehs.db قاعدة SQLite (تُنشأ تلقائياً عند أول تشغيل)
assets/css/style.css الألوان، الخطوط المحلية (TTF)
assets/js/main.js دوال: apiPost, الطرفية، buildPortsTable، وغيرها — لإعادة استخدام نفس البرمجة في الصفحات
assets/fonts/*.ttf IBM Plex Sans Arabic (موجودة محلياً)
templates/*.html واجهات عربية: كل صفحة تمتد من base.html
modules/*_sim.py منطق المحاكاة دون شيفرة هجوم حقيقي — القلب البرمجي لكل تمرين

رابط static: في app.py يُعرَّف static_folder="assets" وstatic_url_path="/static" فالواجهة تستدعي الملفات مثل: /static/css/style.css (انظر url_for في base.html).

متطلبات التشغيل

  • Python 3.10+ (يُنصح بأحدث إصدار ثابت)
  • pip لإدارة الحزم
  • متصفح ويب حديث

خطوات التثبيت والتشغيل

# 1. الانتقال للمجلد
cd D:\VSCode\Projects\EEHS

# 2. تثبيت المتطلبات
pip install -r requirements.txt

# 3. تشغيل التطبيق
python app.py

# 4. فتح المتصفح على
# http://127.0.0.1:5000

إن وُجدت قاعدة قديمة بغير الجداول المطلوبة، احذف الملف eehs.db ثم شغّل التطبيق من جديد ليُعاد إنشاؤها.

النشر على Render

  1. ارفع المشروع إلى مستودع Git بحيث يكون مجلد المشروع (حيث app.py وrender.yaml) هو جذر المستودع، أو اضبط Root Directory في Render إلى ذلك المسار.
  2. في Render: NewBlueprint (أو Web Service يدوياً) واربط المستودع.
  3. Blueprint يقرأ render.yaml: بناء pip install -r requirements.txt ثم تشغيل gunicorn app:app --bind 0.0.0.0:$PORT (المتغير PORT يضبطه Render).
  4. ملاحظة: ملف eehs.db على خطة Free Web يُحفَظ في نظام الملفات المؤقت للمثيل؛ عند إعادة نشر الخدمة أو إعادة إنشاء المثيل قد تُعاد تهيئة البيانات الافتراضية. لإبقاء دائم: قرص مرتبط (مدفوع) أو قاعدة بيانات إدارة (مثلاً PostgreSQL) لاحقاً.
  5. محلياً: python app.py يستمع على 0.0.0.0 والمنفذ من PORT (افتراضي 5000). يمكن تعيين FLASK_DEBUG=1 للتطوير. على Render التشغيل الفعلي عبر gunicorn من startCommand وليس python app.py مباشرة.

كيف يعمل التطبيق من المتصفح حتى قاعدة البيانات

  1. يدخل المستخدم صفحة (مثلاً /nmap). خادم Flask ينفّذ دالة page_nmap التي تقرأ sim_ports مرة لعرض جدول أولي في الصفحة.
  2. يضغط «تشغيل الفحص». JavaScript ينفّذ apiPost('/api/nmap/scan', { target_ip, scan_type }) (من main.js). الطلب POST يحمل JSON في الجسم.
  3. app.py يستدعي simulate_nmap من modules/nmap_sim.py التي:
    • تقرأ كل المنافذ من الجدول،
    • تختار قائمة فرعية حسب النوع (فحص أساسي = سبعة منافذ شائعة؛ فحص إصدارات = اثنا عشر سطراً؛ فحص شامل = الكل + سطر OS وهمي)،
    • تبني output_lines مثل مخرجات Nmap.
  4. الرجوع JSON فيُعرض سطراً بسطر في div#nmap-term (المحاكاة البصرية).
  5. بعد فحص أو هجوم، log_attack تُدخل سطراً في attack_logs ليظهر لاحقاً في لوحة التحكم.
  6. نفس المسار يتكرر في وحدات أخرى: POSTjsonify → رسم نصوص في الطرفية.

مقتطف من app.py (تهيئة التطبيق ولوحة التحكم): يوضح كيف تُسحب الأعداد والأحدث من SQL:

# عند الاستيراد: init_db() تُنفّذ مرة
def _bootstrap():
    init_db()
_bootstrap()

@app.route("/")
def index():
    conn = get_db()
    cur = conn.cursor()
    cur.execute("SELECT COUNT(*) FROM attack_logs")
    total = cur.fetchone()[0]
    # ... success / blocked / vuln_c ...
    cur.execute(
        """
        SELECT attack_type, target_ip, timestamp, result, success
        FROM attack_logs ORDER BY id DESC LIMIT 10
        """
    )
    last = [dict(zip(["attack_type", "target_ip", "timestamp", "result", "success"], r)) for r in cur.fetchall()]
    conn.close()
    return render_template(
        "index.html",
        total_attacks=total,
        success_attacks=ok,
        blocked_attacks=blocked,
        vuln_count=vuln_c,
        last_attacks=last,
    )

الشرح: ORDER BY id DESC LIMIT 10 تعني آخر 10 سجلاً حسب التسلسل. الحقل success يساعد واجهة «ناجح / محجوب» على اللوحة.

دليل الصفحات: كل ميزة وكيف تُحاكى

الصفحة المسار أهم ميزات واجهة ما الذي يُحاكى؟
لوحة التحكم / أربع بطاقات أعداد، جدول آخر هجمات، 6 بطاقات وحدات أعداد من attack_logs + sim_vulnerabilities
Nmap /nmap IP، اختيار نوع فحص، زر فحص، طابور نصوص، جدول قراءة sim_ports، توليد سطور nmap-شكل
Brute /brute-force نام مستخدم، هدف نصي ثابت، طرفية، 4 حماية + اختبار قائمة كلمات وهمية، نجاح msfadmin أو حظر إذا protected
XSS /xss صندوق DVWA، تعقيم، تعليقات، سرقة كوكي نصي check_xss + sanitize_input + جدول xss_payloads
تشويه /defacement معاينة HTML، اختيار ملف، تقييد امتداد simulate_upload / apply_protection + upload_attempts
نص صريح /cleartext رقائق أوامر MySQL، جدول مقارنة، زر bcrypt get_cleartext_users + فلتر md5_hex + get_hashed_users
Metasploit /metasploit banner، رقائق أوامر، exploit، حماية ثلاثية simulate_metasploit + protected
حول /about جدول بيانات المشروع + تنبيه أخلاقي ثابت في القالب
واجهة برمجة /api/... JSON فقط (لا واجهة) يرتبط بكل الأعلى

ملاحظة: صفحات about وindex لا تُنفّذ «هجوماً» — تعرض بياناتاً أو وصفاً.

شرح الوحدات التعليمية (تفصيل + مقتطفات + مصطلحات)

1) Nmap (فحص الشبكة / الاستطلاع)

  • المفهوم: معرفة المنافذ المفتوحة والخدمات يقلل المجهول ويخبرك أين تبحث لاحقاً.
  • كيف في الواقع؟ أداة Nmap ترسل حزماً وتحلل الردود. هنا: النتائج قادمة من جدول sim_ports وتُبنى نصوص تشبه مخرجات Nmap.
  • دالة رئيسية في المشروع: simulate_nmap(scan_type, target_ip) في modules/nmap_sim.py — تُرجع output_lines وports وos_guess.
  • حماية: جدار (محاكاة) عبر POST /api/nmap/protect يُسجّل سطراً في attack_logs ويُرجع قائمة منافذ مُغلقة وهمية.
مصطلح تعريف مثال
منفذ (Port) رقم يربط تطبيقاً بخدمة على IP 80/HTTP
الخدمة (Service) البرنامج المستمع (Apache, ssh…) Apache 2.2.8
-sV فحص إصدارات (في nmap الحقيقي) في المحاكاة يُقارن بعرض 12 سطراً من المنافذ
-A -T4 تجميع (إصدارات + OS) مع سرعة (في nmap) في المحاكاة: كل المنافذ + سطر OS details وهمي

كيف تختار النتائج الداخلية؟ الجزء التالي من الكود (مختصر) يوضح: الفحص «الأساسي» يعيد قائمة مفلترة من عناوين شائعة؛ «الإصدارات» يأخذ 12 مرفقاً أولى؛ «الشامل» يأخذ الكل (مع محاكاة OS في النصوص).

# من modules/nmap_sim.py
st = str(scan_type)
if st in ("basic", "فحص أساسي"):
    show = [p for p in ports_out if p["port"] in (21, 22, 23, 80, 443, 3306, 445)]
elif st in ("sv", "فحص إصدارات", "-sV"):
    show = ports_out[:12]
else:
    show = ports_out
# ثم تُركّب كل سطر من show في سطور مثل:  21/tcp  open  ftp  vsftpd 2.3.4

وظيفة firewall_closed_ports: تُرجع قائمة أرقام (محاكاة) لعرض «ما أُغلق» دون تعديل أي جدول فعلي — الهدف تعليمي (إدراك سطح الهجوم).

2) Brute Force (Hydra / SSH)

  • المفهوم: جرب عدة كلمات مرور تلقائياً — خطر قفل الحساب وسجلات وحظر IP — والحلول: Fail2Ban، كلمات قوية، 2FA، حد أقصى للمحاولات.
  • في المشروع: simulate_brute_force تُرجع محاولات وهمية؛ عند protected=True تظهر رسالة حظر بعد 3 فشل.
  • حماية: Fail2Ban، كلمة قوية، 2FA، قفل بعد محاولات — مُوضحة كواجهة تفاعلية (مخرجات وهمية).
الدالة المعاملات المُرجع وصف
simulate_brute_force username, target, protected attempts, success, password حركة وهمية لسجل Hydra — target يُسجّل فقط ولا يُتصل به

مقتطف من modules/brute_force_sim.py: منطق «مع حماية» و«بدون»:

def simulate_brute_force(username, target, protected=False):
    dict_words = ["password", "123456", "qwerty", "letmein", "admin", "msfadmin"]
    if protected:
        for w in dict_words[:3]:
            attempts.append({"line": f"[ATTEMPT] {username}:{w} → FAILED", "ok": False})
        attempts.append({"line": "[BLOCKED] IP حُظر بعد 3 محاولات فاشلة", "blocked": True, ...})
        return { "success": False, ... }
    for w in dict_words:
        if w != "msfadmin":
            attempts.append({"line": f"[ATTEMPT] {username}:{w} → FAILED", "ok": False})
    attempts.append({"line": f"[SUCCESS] login: {username}  password: msfadmin", "ok": True})
    return { "success": True, "password": "msfadmin", ... }

للمبتدئ: password الظاهر في «النجاح» ثابت في المحاكاة ولا يعني أن أي خادم SSH حقيقي يقبل msfadmin — فقط يعلمك شكل سطر نجاح Hydra.

3) XSS

  • المفهوم: حقن سكربت/وسوم تُنفّذ في متصفح الضحية — الحل: تعقيم المخرجات، CSP، وعلى الأقل HttpOnly للكوكي.
  • في المشروع: check_xss يفحص أنماطاً شائعة بـ regex؛ sanitize_input يستبدل & و< و> وعلامات الاقتباس — نفس فكرة htmlspecialchars في PHP. عند تفعيل «الحماية» في الصفحة، المخرج نص لا يُفسَّر HTML.
  • مخزن: يُحفَظ في xss_payloads عندما يكون payload_type يساوي مخزن (أو stored مُعادلة في الطلب) ويُستعاد بـ GET /api/xss/stored.
مصطلح تعريف مثال
Reflected (ينعكس) المدخلات تُرجع في نفس الرد فوراً ?name=...
DOM تغيير الصفحة بـ JavaScript دون الاعتماد فقط على خادم معالجات أحداث في المتصفح
CSP تعليمات للمتصفح: من أين يسمح بتحميل سكربت/صور default-src 'self' في المحاكاة
HttpOnly يمنع document.cookie من رؤية الكوكي (من JavaScript) يساعد لكن لا يكفي لوحده لكل XSS

مقتطف من modules/xss_sim.py (بداية check_xss):

patterns = [
    (r"(?i)<\s*script", "Script tag", "عالي", "مُعكوس/مخزن"),
    (r"(?i)on\w+\s*=", "معالج حدث (onerror...)", "عالي", "DOM/Reflected"),
    # ...
]
for reg, name, sev, typ in patterns:
    m = re.search(reg, p)
    if m:
        return { "is_xss": True, "pattern_found": m.group(0)[:80], ... }

شرح مبسّط: re.search يبحث عن شكل في النص. المحاكاة ليست كاشف 100% لكل الاختراقات في العالم — بل نموذج تعليمي.

4) Defacement (تشويه + رفع)

  • المفهوم: رفع ملف يسمح بتنفيذ أوامر (مثلاً .php) على خادم ويب — ثم تغيير الصفحة الرئيسية. الحماية: فلتر امتدادات، AddType، تعطيل محرك PHP في موقع معين.
  • في المشروع: simulate_upload وapply_protection — القرار allowed يعتمد على الامتداد ومفعّل التقييد؛ الجدول upload_attempts يسجّل المحاولة.
  • الامتدادات: المخاطرة: php… | المسموح عند التقييد: jpg/png/gif.
  • حماية: نص apply_protection يُظهر مثال .htaccess تعليمي (في الواجهة وفي JSON عند POST /api/defacement/protect).

فكرة منطق (من defacement_sim.py باختصار): إن كان التقييد مفعّلاً وامتداد الملف ليس في القائمة البيضاء → رفض؛ وإن كان php وبدون حماية → تُرجع exec_output بسلسلة تحاكي id وHACKED.

5) نص صريح (تخزين كلمات المرور)

  • المفهوم: تخزين password كنص يسمح لمن يهرب قاعدة البيانات بالدخول مباشرة؛ bcrypt يُبطئ التخمين ويحتاج أدوات ثقيلة، خاصة عند cost مناسب.
  • في المشروع: get_cleartext_users / get_hashed_users من sim_users؛ في الجدول يُظهر عمود MD5 عبر فلتر md5_hex (قيم مقصوصة لعرض الضعف — MD5 لا يُنصح به لتخزين كلمات مرور اليوم)؛ password_hash bcrypt حقيقي مُخزّن.
# من app.py — عرض تعليمي فقط: مقارنة بصمات
@app.template_filter("md5_hex")
def _md5_hex(s):
    return hashlib.md5(s.encode("utf-8")).hexdigest()[:10] + "…"
  • كود فكري في القالب (PHP): password_hash / password_verify كما في صفحة cleartext.html.
مصطلح تعريف مثال
Rainbow table جدول: hash → نصوص شائعة لتسريع «البحث» يساعد كسر MD5 السريع
بذرة (Salt) + تكلفة (cost) يزيدان تفرّد الـ hash ويُبطئان التخمين bcrypt في password_hash
KDF آمن (باختصار) وظيفة مقصودة أن تكون بطيئة bcrypt/argon2

6) Metasploit

  • المفهوم: اختيار استغلال (exploit) + خيارات (مثل RHOSTS الهدف) + تشغيل → في النظام الحقيقي: جلسة meterpreter أو shell.
  • في المشروع: simulate_metasploit — عند protected=False تُرجع session_open: True وسطوح بـ id وroot نص؛ عند protected=True فشل اتصال (محاكاة تعليمية).
  • Chips في الصفحة تولّد سطور تشبه msfconsole (أمر use وset RHOSTS…).

فكرة من metasploit_sim.py (جزء إيجابي): إن لم يُحمَِ الهدف → سطور بانر، محاولة، Connected to backdoor، session 1، وما يشبه أوامر id / whoami — كلها نص يُرسل للواجهة.

قاعدة البيانات

الجداول: attack_logs, sim_users, sim_ports, sim_vulnerabilities, xss_payloads, upload_attempts — حسب database.py.

  • مستخدمون (10): بيانات افتتاحية + bcrypt لكل password_hash عبر hash_bcrypt في init_db — الحقل password_plain للدرس على خطر التخزين النصي.
  • منافذ (20) وثغرات (10): كما في مواصفات المشروع — تُطابق فكرياً بيئات مثل Metasploitable (أسماء خدمات وإصدارات قديمة عمداً).
  • سجلات هجوم (15): داخل init_db لآخر 30 يوماً تقريباً — تشغل أولاً لوحة التحكم حتى بدون تفاعل.
  • سجلات جديدة: تُضاف عند استدعاء log_attack من app.py بعد فحوصات/هجمات محاكاة.

مقتطف من log_attack (فكرة الحفظ): يدخل attack_type وtarget_ip وresult وsuccess (1=من منظور «نجاح المحاكاة/الوصف» حسب المسار) وdetails_json اختياري.

cur.execute(
    """
    INSERT INTO attack_logs
    (attack_type, target_ip, timestamp, result, details_json, success)
    VALUES (?, ?, ?, ?, ?, ?)
    """,
    (attack_type, target_ip, now, result, details_json, 1 if success else 0),
)

API Endpoints

طريقة المسار وصف مثال مُرجع (جزئي)
GET / لوحة التحكم HTML
GET /nmap صفحة Nmap
POST /api/nmap/scan فحص وهمي {"output_lines":[], "ports":[]}
POST /api/nmap/protect جدار ناري (محاكاة) {"closed_ports":[]}
POST /api/brute-force/attack Brute + حماية {"success": true/false, "attempts":[]}
GET/POST /xss و /api/xss/* حقن، تعقيم، مخزن {"check":{}, "sanitize":{}}
GET /api/xss/stored تعليقات {"items":[]}
POST /api/defacement/upload رفع (JSON اسم/امتداد) {"allowed", "exec_output"}
POST /api/defacement/protect .htaccess {"htaccess_content"}
POST /api/cleartext/query أوامر MySQL وهمية {"output"} + users عند SELECT
POST /api/cleartext/hash وضع bcrypt {"users":[]}
POST /api/metasploit/exploit استغلال {"output_lines":[]}
GET /api/stats أرقام لوحة {"total_attacks",...}

للمبتدئ عند اختبار API يدوياً: يُفضّل Postman أو تبويب «Network» في المتصفح لرؤية نفس الطلبات التي ترسلها الصفحات (طريقة POST ورأس Content-Type: application/json).

دليل التصميم

  • الخط: IBM Plex Sans Arabic من assets/fonts (ملفات .TTF محلية) عبر format('truetype') في style.css.
  • الألوان: متغيرات CSS كما في الجذر (--bg-primary, --green …) — يسهل تغيير السِمة دون تعديل كل الصفحات.
  • الأزرار: border-radius: 8px، padding: 8px 18px كحد.
  • البطاقات: 12px، حد 1px solid #30363d، بدون شريط جانبي ديكوري.
  • اتجاه: rtl على html وbody — النصوص من اليمين؛ الطرفية ltr فالأوامر إنجليزية مثل nmap.
  • حركة: انتقال 200ms فقط (hover) — تقليل التشويت البصري ليركز المتعلم على المحتوى.

من main.js (أدوات مشتركة): apiPost يرسل JSON؛ appendToTerminal يضيف div داخل وعاء الطرفية ويُحريك التمرير للأسفل — هذا يمنح إحساس كتابة سطر دون تثبيت حزم طرفية في النظام.

async function apiPost(url, data) {
  const res = await fetch(url, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data)
  });
  return await res.json();
}

ملاحظة أخلاقية وقانونية

يقتصر الاستخدام على التدريب والبيئات المصرّح بها أو المعزولة. الدخول غير المصرح به لأنظمة حقيقية جريمة في أغلب التشريعات. EEHS لا ينفّذ هجمات حقيقية ولا يستهدف عناوين خارجة عن المحاكاة.

نتائج المشروع

  • واجهة عربية موحّدة ولوحة إحصاء من attack_logs.
  • ست وحدات تربط نظرية + محاكاة + حماية لكل سيناريو.
  • قاعدة بيانات تُظهر مخاطر التخزين الصريح وقوة bcrypt في سياق تعليمي.

معجم مصطلحات بأمثلة عملية (مختصر ومناسب للمبتدئ)

مصطلح ماذا يعني ببساطة؟ مثال «من الحياة» / من EEHS
JSON تنسيق نصي لتبادل بيانات بين الخادم والمتصفح الرد من /api/nmap/scan هو JSON
الـ API واجهة program تتكلم بها أنت أو الواجهة كل مسار يبدأ بـ /api/
SQLite قاعدة بيانات في ملف واحد eehs.db بجانب المشروع
Flask إطار Python لبناء خادم ويب + مسارات app = Flask(__name__)
Regex (تعبير نمطي) وصف شكل نص (مثلاً “يبدأ بـ <”) check_xss يفحص <script
Hash (بصمة) مخرجات دالة غير عكسيّة بسهولة من النص MD5/SHA لشرح الضعف — bcrypt للتخزين الجيد
CWE/CVE تصنيف ضعف / رقم مُعرّف لثغرة مُوثّقة sim_vulnerabilities تذكر CVE-...
Shell / Terminal واجهة سطر أوامر div.terminal تشبه الشاشة سوداء/خضراء فقط عرضياً

تذكير: الفصل بين ما يفعله المشروع (نص + قاعدة) وما يفعله المختبر الحقيقي (Kali, Metasploitable) يبني فهماً صحيحاً ويقلل الأخطاء الأخلاقية.

المراجع (أمثلة تعليمية)

  1. Nmap — دليل الاستخدام الرسمي (nmap.org).
  2. OWASP — XSS ومعالجة المدخلات.
  3. CVE-2011-2523 — vsftpd 2.3.4 (للسياق التاريخي).
  4. Metasploit Unleashed — مقدمة تعليمية.
  5. PCI-DSS — مبدأ تخزين بيانات اعتماد آمن.
  6. RFC 5246/8446 (TLS) — فهم لماذا النص الصريح يُعرّض الاعتمادات.
  7. CWE-79 (XSS) — تصنيف الضعف.
  8. VirtualBox — توثيق الشبكات المعزولة (Host-Only) للمختبرات.

جامعة بيشة — قسم الأمن السيبراني — 2025-2026

About

مشروع تخرج محاكي اختراق أخلاقي تعليمي - جامعة بيشة

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages